package com.heima.service.impl;

import com.heima.entity.SeckillGoods;
import com.heima.entity.SeckillMessage;
import com.heima.entity.SeckillOrder;
import com.heima.mapper.SeckillGoodsMapper;
import com.heima.mapper.SeckillOrderMapper;
import com.heima.service.SeckillService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper;

    @Autowired
    private SeckillOrderMapper seckillOrderMapper;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 系统启动时自动执行库存预热
    @PostConstruct
    public void init() {
        preloadGoodsToRedis();
        System.out.println("库存预热完成，已将数据库商品库存同步到Redis");
    }

    // 库存预热到Redis（完善此方法）
    public void preloadGoodsToRedis() {
        // 1. 从数据库查询所有有效的秒杀商品（例如：未开始或正在进行的秒杀）
        List<SeckillGoods> seckillGoodsList = seckillGoodsMapper.selectAllValidSeckillGoods();
        if (seckillGoodsList == null || seckillGoodsList.isEmpty()) {
            return;
        }

        // 2. 遍历商品，将库存缓存到Redis
        for (SeckillGoods goods : seckillGoodsList) {
            Long goodsId = goods.getGoodsId();
            Integer stockCount = goods.getStockCount();

            // 缓存库存：key为"seckill:stock:商品ID"，value为库存数量
            redisTemplate.opsForValue().set(
                    "seckill:stock:" + goodsId,
                    stockCount,
                    // 过期时间可设置为秒杀活动结束时间（确保库存缓存不会长期无效）
                    goods.getEndDate().getTime() - System.currentTimeMillis(),
                    TimeUnit.MILLISECONDS
            );

//            // 可选：同时缓存商品其他信息（如秒杀价），方便后续使用
//            redisTemplate.opsForValue().set(
//                    "seckill:info:" + goodsId,
//                    goods,
//                    goods.getEndDate().getTime() - System.currentTimeMillis(),
//                    TimeUnit.MILLISECONDS
//            );
        }
    }


    @Override
    public String createPath(Long userId, Long goodsId) {
        // 生成动态路径
        String path = generateRandomPath(userId, goodsId);
        // 缓存路径到Redis，设置过期时间
        redisTemplate.opsForValue().set(
                "seckill:path:" + userId + ":" + goodsId,
                path,
                3600,
                TimeUnit.SECONDS
        );
        return path;
    }

    // 检查动态路径
    @Override
    public boolean checkPath(Long userId, Long goodsId, String path) {
        String cachedPath = (String) redisTemplate.opsForValue().get(
                "seckill:path:" + userId + ":" + goodsId
        );
        return path != null && path.equals(cachedPath);
    }

    // 执行秒杀
    @Override
    public String executeSeckill(Long userId, Long goodsId) {
        // 1. 接口限流（不使用Lua脚本）
        String limitKey = "seckill:limit:" + userId;
        // 1.1 计数+1（原子操作）
        Long currentCount = redisTemplate.opsForValue().increment(limitKey, 1);
        // 1.2 如果是第一次计数，设置过期时间（1秒）
        if (currentCount != null && currentCount == 1) {
            redisTemplate.expire(limitKey, 1, TimeUnit.SECONDS);
        }
        // 1.3 判断是否超过阈值（1秒最多10次）
        if (currentCount != null && currentCount > 10) {
            return "请求过于频繁，请稍后再试";
        }

        // 2. 检查用户是否已购买：仅查Redis，不碰数据库
        String boughtKey = "seckill:bought:" + goodsId;
        // 2.1 先查Redis：判断用户是否在已购买集合中
        Boolean isBought = redisTemplate.opsForSet().isMember(boughtKey, userId);
        if (Boolean.TRUE.equals(isBought)) {
            return "您已购买过该商品";
        }

        // 3. Redis预扣减库存（不使用Lua脚本）
        String stockKey = "seckill:stock:" + goodsId;
        // 3.1 库存-1（原子操作）
        Long remainingStock = redisTemplate.opsForValue().decrement(stockKey);
        // 3.2 判断库存是否不足（如果为null，说明库存未预热）
        if (remainingStock == null || remainingStock < 0) {
            // 库存不足时，恢复计数（避免误扣）
            if (remainingStock != null) {
                redisTemplate.opsForValue().increment(stockKey, 1);
            }
            return "商品已售罄";
        }

        // 新增：记录用户已进入队列（设置过期时间，如10分钟，避免永久缓存）
        redisTemplate.opsForValue().set(
                "seckill:user:queue:" + userId + ":" + goodsId,
                "waiting",  // 状态：等待中
                600,
                TimeUnit.SECONDS
        );

        // 4. 发送消息到RabbitMQ，异步创建订单
        SeckillMessage message = new SeckillMessage();
        message.setUserId(userId);
        message.setGoodsId(goodsId);
        rabbitTemplate.convertAndSend("seckill.exchange", "seckill.route", message);

        return "排队中...";
    }

    //查询秒杀结果
    @Override
    public String getSeckillResult(Long userId, Long goodsId) {
        // 1. 先查Redis中的用户状态（优先判断，减少数据库访问）
        String userStatus = (String) redisTemplate.opsForValue().get(
                "seckill:user:queue:" + userId + ":" + goodsId
        );

        if (userStatus == null) {
            // 状态不存在：用户未参与秒杀或已超时
            return "未参与秒杀";
        }

        if (userStatus.startsWith("success:")) {
            // 状态为成功：直接返回订单号
            String orderId = userStatus.split(":")[1];
            return "秒杀成功，订单号：" + orderId;
        }

        if (userStatus.equals("failed")) {
            // 状态为失败：返回失败信息
            return "秒杀失败";
        }

        // 状态为等待中：检查Redis库存是否已为0
        Long stock = (Long) redisTemplate.opsForValue().get("seckill:stock:" + goodsId);
        if (stock != null && stock <= 0) {
            // 库存为0，但用户仍在队列中：继续等待（队列可能还在处理）
            return "排队中...";
        }

        // 其他情况：继续等待
        return "排队中...";
    }

    private String generateRandomPath(Long userId, Long goodsId) {
        // 1. 结合用户ID、商品ID和随机字符串，增强唯一性
        String randomStr = UUID.randomUUID().toString().replace("-", "");
        // 2. 可以加入加密逻辑（如MD5），防止路径被猜测
        String raw = userId + ":" + goodsId + ":" + randomStr + ":" + System.currentTimeMillis();
        return DigestUtils.md5DigestAsHex(raw.getBytes()); // 使用Spring的工具类生成MD5
    }
}